/*
 * Terminador.cpp
 *
 */

#include "Terminador.h"

/**
 * Constructor del terminador.
 * Recibe el tiempo que debe durar la simulacion.
 * Registra los handlers de seniales.
 */
Terminador::Terminador( int tiempo )
	: tiempo ( tiempo ),
	  seguirEjecutando ( true ),
	  enEjecucion ( false ),
	  error ( false ),
	  pidAscensor ( 0 ),
	  pidControlador ( 0 ),
	  pidPisos ( list<pid_t>() ){

	SignalHandler::getInstance()->registrarHandler( SIGALRM, this );
	SignalHandler::getInstance()->registrarHandler( SIGINT, this );
	SignalHandler::getInstance()->registrarHandler( SIGTERM, this );
	SignalHandler::getInstance()->registrarHandler( SIGUSR1, this );

}

/**
 * Destructor del terminador.
 * Elimina los handlers de seniales.
 */
Terminador::~Terminador() {
	SignalHandler::getInstance()->removerHandler ( SIGALRM );
	SignalHandler::getInstance()->removerHandler ( SIGINT );
	SignalHandler::getInstance()->removerHandler ( SIGTERM );
	SignalHandler::getInstance()->removerHandler ( SIGUSR1 );
	SignalHandler::destruir();
}

/**
 * Handler de seniales:
 * SIGALRM: Senial que el terminador se envia a si mismo para saber cuando
 * 		se termina el tiempo de simulacion.
 * SIGTERM: Senial que el usuario envia desde fuera de la aplicacion.
 * 		El terminador comienza la finalizacion de la ejecucion ordenadamente.
 * SIGINT: Senial que el usuario envia desde fuera de la aplicacion.
 * 		El terminador comienza la finalizacion de la ejecucion ordenadamente.
 * SIGUSR1: Senial que envian los hijos para notificar errores.
 * 		El terminador inicia la finalizacion de los hijos inmediatamente.
 */
int Terminador::handleSignal( int signum ){
	if ( signum == SIGALRM ) {
		if ( this->seguirEjecutando ){
			Log::getInstance()->loguear( "Terminador: me llego SIGALRM. Se termino el tiempo de simulacion." );
			Log::getInstance()->imprimir( "Se termino el tiempo de simulacion." );
			this->seguirEjecutando = false;
		}

	} else if ( signum == SIGTERM ){
		if ( this->seguirEjecutando ){
			Log::getInstance()->loguear("Terminador: me llego SIGTERM. Voy a finalizar.");
			Log::getInstance()->imprimir( "Usuario detiene la simulacion." );
			this->seguirEjecutando = false;
		}

	} else if ( signum == SIGINT ) {
		if ( this->seguirEjecutando ){
			Log::getInstance()->loguear( "Terminador: me llego SIGINT. Voy a finalizar." );
			Log::getInstance()->imprimir( "Usuario detiene la simulacion." );
			this->seguirEjecutando = false;
		}

	} else if ( signum == SIGUSR1 ){
		Log::getInstance()->loguear( "Terminador: me llego SIGUSR1. Voy a finalizar con errores." );
		Log::getInstance()->imprimir( "Se termina la simulacion por errores." );
		if ( this->enEjecucion ){
			this->exterminar();
		} else {
			this->error = true;
		}
	}
	return 0;
}

/**
 * Metodo que se ejecuta mientras quede tiempo de simulacion o no hayan
 * llegado notificaciones de error desde los procesos hijos.
 */
void Terminador::ejecutar(){
	this->enEjecucion = true;
	if ( this->error ){
		this->exterminar();
	}
	Log::getInstance()->imprimir( "Comienza la simulacion. " );
	Log::getInstance()->loguear( "Terminador: me duermo " + intToString( this->tiempo) + " segundos." );
	alarm ( this->tiempo );
	while ( this->seguirEjecutando ){
		pause();
	}
	this->terminar();
}

/**
 * Metodo que envia seniales SIGUSR2 a todos los pisos para que
 * finalicen la ejecucion correctamente.
 * Una vez eliminados los pisos, se espera a que el controlador
 * finalice solo, y se envia la senial SIGUSR2 al ascensor para
 * que termine con las llamadas pendientes.
 */
void Terminador::terminar(){
	list<pid_t>::iterator it = this->pidPisos.begin();
	for ( ; it != this->pidPisos.end() ; ++it ){
		kill( *it, SIGUSR2 );
	}
	waitpid( this->pidControlador, NULL, 0 );
	kill( this->pidAscensor, SIGUSR2 );
}

/**
 * Metodo que envia seniales SIGABRT a todos los procesos hijos para
 * que finalicen la ejecucion inmediatamente.
 * El terminador libera los recursos y también finaliza inmediatamente.
 */
void Terminador::exterminar(){
	list<pid_t>::iterator it = this->pidPisos.begin();
	for ( ; it != this->pidPisos.end() ; ++it ){
		kill( *it, SIGABRT );
	}
	kill( this->pidControlador, SIGABRT );
	kill( this->pidAscensor, SIGABRT );

	this->~Terminador();
	exit ( -1 );
}

void Terminador::setPidAscensor( pid_t pidAscensor ){
	this->pidAscensor = pidAscensor;
}

pid_t Terminador::getPidAscensor() const{
	return this->pidAscensor;
}

void Terminador::setPidControlador( pid_t pidControlador ){
	this->pidControlador = pidControlador;
}

pid_t Terminador::getPidControlador() const{
	return this->pidControlador;
}

void Terminador::agregarPidPiso( pid_t pidPiso ){
	this->pidPisos.push_back( pidPiso );
}
